#include "stdafx.h"
#include "SphericubicMesh.h"

namespace Sphericubic
{
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	SphericubicMesh::SphericubicMesh(const EType& _eType, D3DRenderer& _rRenderer)
	:	OrbitingObject(_eType),
		m_rRenderer(_rRenderer),
		m_pVertexBuffer(NULL),
		m_pIndexBuffer(NULL),
		m_vFaceToRenderList(),
		m_pVertexes(NULL),
		m_pVertexDecl(NULL),
		m_pFaces(NULL),
		m_pIndexes(NULL),
		m_uSubdivisions(0),
		m_uVerticesCount(0),
		m_uFacesCount(0),
		m_uIndexesCount(0),
		m_uVerticesPerRow(0)
	{
		D3DXMatrixIdentity( &m_oLocalPos );
		D3DXMatrixIdentity( &m_oLocalRot );
		D3DXMatrixIdentity( &m_oWorldTransf );
		m_oRotation.x = m_oRotation.y = m_oRotation.z = 0.0f;
		m_oPosition.x = m_oPosition.y = m_oPosition.z = 0.0f;
	}

	SphericubicMesh::~SphericubicMesh()
	{

	}

	bool SphericubicMesh::Create(const unsigned int& _uSubdivisions, const float& _fScale)
	{
		bool bResult = false;

		if ( ( NULL == m_pVertexes )
			&& ( NULL == m_pFaces )
			&& ( NULL == m_pVertexBuffer )
			&& ( NULL == m_pIndexBuffer )
			&& ( NULL == m_pIndexes ) )
		{
			m_uSubdivisions = _uSubdivisions;
			m_fScale = _fScale;
			CreateMeshVertices();
			MorphToSphere();
			UpdateBoundingSize();
			CreateMeshFaces();
			bResult = CreateVertexBuffer();
		}

		return bResult;
	}

	void SphericubicMesh::Release()
	{
		if ( NULL != m_pVertexDecl )
		{
			m_pVertexDecl->Release();
			m_pVertexDecl = NULL;
		}
		if ( NULL != m_pVertexBuffer )
		{
			m_pVertexBuffer->Release();
			m_pVertexBuffer = NULL;
		}
		if ( NULL != m_pIndexBuffer )
		{
			m_pIndexBuffer->Release();
			m_pIndexBuffer = NULL;
		}
		if ( NULL != m_pIndexes )
		{
			delete[] m_pIndexes;
			m_pIndexes = NULL;
		}
		if ( NULL != m_pVertexes )
		{
			delete[] m_pVertexes;
			m_pVertexes = NULL;
		}
		if ( NULL != m_pFaces )
		{
			delete[] m_pFaces;
			m_pFaces = NULL;
		}
	}

	void SphericubicMesh::SetPosition(const Vector3& _oPosition)
	{
		m_oPosition = _oPosition;
	}

	void SphericubicMesh::SetRotation(const Vector3& _oRotation) // in degree
	{
		m_oRotation = _oRotation;
	}

	void SphericubicMesh::Update(const float& _fTimeSpeed)
	{
		OrbitingObject::Update( _fTimeSpeed );
		m_oWorldTransf = m_oWorld;
	}

	void SphericubicMesh::Begin()
	{
		OrbitingObject::Begin();

		if ( ( ELevelOfDetail_POINT != m_eLevelOfDetail )
			&& ( ELevelOfDetail_BILLBOARD != m_eLevelOfDetail ) )
		{
			m_vFaceToRenderList.clear();

			// invalidate all previous world transformed vertexes.
			Vertex* pVertex = m_pVertexes;
			for ( unsigned int i = 0 ; m_uVerticesCount > i ; ++i, ++pVertex )
			{
				pVertex->m_oFromCamDir.x = FLT_MAX;
			}

			const Matrix& rView = m_rRenderer.GetViewMatrix();
			const Vector3 oCamPos( rView._41, rView._42, rView._43 );

			Face* pFace = m_pFaces;
			for ( unsigned int i = 0 ; m_uFacesCount > i ; ++i, ++pFace )
			{
				// test if face is visible from camera direction.
				if ( false != pFace->IsFacingDir( oCamPos, m_oWorldTransf ) )
				{
					// add face for future rendering.
					m_vFaceToRenderList.push_back( pFace );
				}
			}
		}
	}

	void SphericubicMesh::PreRender(LPD3DXEFFECT _pEffect, const D3DXPASS_DESC& _rPassDesc)
	{
	}

	void SphericubicMesh::Render()
	{
		HRESULT hResult = m_rRenderer.GetDevice()->SetVertexDeclaration( m_pVertexDecl );
		if ( FAILED( hResult) )
		{
			return;
		}
		hResult = m_rRenderer.GetDevice()->SetStreamSource( 0, m_pVertexBuffer, 0, sizeof( Vertex ) );
		if ( FAILED( hResult) )
		{
			return;
		}
		hResult = m_rRenderer.GetDevice()->SetIndices( m_pIndexBuffer );
		if ( FAILED( hResult) )
		{
			return;
		}

		if ( ELevelOfDetail_POINT == m_eLevelOfDetail )
		{
			m_rRenderer.GetDevice()->DrawIndexedPrimitive( D3DPT_POINTLIST, 0, 0, m_uVerticesCount, 0, 1 );
		}
		else if ( ELevelOfDetail_BILLBOARD == m_eLevelOfDetail )
		{
			m_rRenderer.GetDevice()->DrawIndexedPrimitive( D3DPT_POINTLIST, 0, 0, m_uVerticesCount, 0, 1 );
		}
		else
		{
			FacePtrVec::iterator iFacePtr = m_vFaceToRenderList.begin();
			FacePtrVec::iterator iEnd = m_vFaceToRenderList.end();
			while ( iEnd != iFacePtr )
			{
				Face* pFace = *iFacePtr;
				m_rRenderer.GetDevice()->DrawIndexedPrimitive( D3DPT_LINESTRIP, 0, 0, m_uVerticesCount, pFace->m_uIndexOffset, 3 );
				++iFacePtr;
			}
		}
	}

	void SphericubicMesh::GetDepthRadiusModifiers(float& _fZNear, float& _fZFar) const
	{
		// no need to modify znear
		//_fZNear = 1.0f;
		// we can only see half of the sphere, we don't need to fully extend the depth range to the other side.
		//_fZFar = 0.0f;
		//_fZFar = 1.0f;
	}

	const unsigned int& SphericubicMesh::GetFaceCount() const
	{
		return m_uFacesCount;
	}

	const SphericubicMesh::Face* SphericubicMesh::GetFaces() const
	{
		return m_pFaces;
	}

	void SphericubicMesh::CreateMeshVertices()
	{
		m_uVerticesPerRow = m_uSubdivisions + 1;
		const unsigned uVerticesPerSide = m_uVerticesPerRow * m_uVerticesPerRow;
		m_uVerticesCount = 6 * uVerticesPerSide;
		m_pVertexes = new Vertex[m_uVerticesCount];

		struct VertexDispatcher
		{
			unsigned int	m_uStartIndex;
			unsigned int	m_aChannels[3];
			float			m_aChannelsCoeff[3];
			float			m_aStepCoeff[3];

			void Do(Vertex* _pVertices, const unsigned int& _uIndexOffset, const float& _fStepA, const float& _fStepB, const float& _fStepC)
			{
				Vertex& rVertex = _pVertices[m_uStartIndex + _uIndexOffset];
				const unsigned int uIndexA = m_aChannels[0];
				const unsigned int uIndexB = m_aChannels[1];
				const unsigned int uIndexC = m_aChannels[2];
				rVertex.m_afOnCube[uIndexA] = m_aChannelsCoeff[uIndexA] + m_aStepCoeff[uIndexA] * _fStepA;
				rVertex.m_afOnCube[uIndexB] = m_aChannelsCoeff[uIndexB] + m_aStepCoeff[uIndexB] * _fStepB;
				rVertex.m_afOnCube[uIndexC] = m_aChannelsCoeff[uIndexC] + m_aStepCoeff[uIndexC] * _fStepC;
			}
		};

		VertexDispatcher aDispatch[] =
		{
			// front
			{	0 * uVerticesPerSide,	{ 0, 1, 2 },	{ -1.0f, +1.0f, -1.0f },	{ +1.0f, -1.0f, 0.0f },	},
			// back
			{	1 * uVerticesPerSide,	{ 0, 1, 2 },	{ +1.0f, +1.0f, +1.0f },	{ -1.0f, -1.0f, 0.0f },	},
			// top
			{	2 * uVerticesPerSide,	{ 0, 2, 1 },	{ -1.0f, +1.0f, +1.0f },	{ +1.0f, 0.0f, -1.0f },	},
			// bottom
			{	3 * uVerticesPerSide,	{ 0, 2, 1 },	{ +1.0f, -1.0f, +1.0f },	{ -1.0f, 0.0f, -1.0f },	},
			// left
			{	4 * uVerticesPerSide,	{ 2, 1, 0 },	{ -1.0f, +1.0f, +1.0f },	{ 0.0f, -1.0f, -1.0f },	},
			// right
			{	5 * uVerticesPerSide,	{ 2, 1, 0 },	{ +1.0f, +1.0f, -1.0f },	{ 0.0f, -1.0f, +1.0f },	},
		};

		const float fStep = 2.0f / m_uSubdivisions;
		for ( unsigned int i = 0 ; uVerticesPerSide > i ; ++i )
		{
			const float fStepA = fStep * ( i % m_uVerticesPerRow );
			const float fStepB = fStep * ( i / m_uVerticesPerRow );
			const float fStepC = 0.0f;
			aDispatch[0].Do( m_pVertexes, i, fStepA, fStepB, fStepC );
			aDispatch[1].Do( m_pVertexes, i, fStepA, fStepB, fStepC );
			aDispatch[2].Do( m_pVertexes, i, fStepA, fStepB, fStepC );
			aDispatch[3].Do( m_pVertexes, i, fStepA, fStepB, fStepC );
			aDispatch[4].Do( m_pVertexes, i, fStepA, fStepB, fStepC );
			aDispatch[5].Do( m_pVertexes, i, fStepA, fStepB, fStepC );
		}
	}

	void SphericubicMesh::MorphToSphere()
	{
		Vertex* pVertex = m_pVertexes;
		for ( unsigned int i = 0 ; m_uVerticesCount > i ; ++i, ++pVertex )
		{
			SphereMapping( *pVertex );
		}
	}

	void SphericubicMesh::UpdateBoundingSize()
	{
		const float fSize = 2.0f * m_fScale;
		m_oBoundingSize.x = fSize;
		m_oBoundingSize.y = fSize;
		m_oBoundingSize.z = fSize;
	}

	void SphericubicMesh::SphereMapping(Vertex& _rVertex)
	{
		const float aConstVertex[] = { _rVertex.m_afOnCube[0], _rVertex.m_afOnCube[1], _rVertex.m_afOnCube[2] };

		assert( aConstVertex[0] >= -1.0f && aConstVertex[0] <= 1.0f && "Invalic cube x range\n" );
		assert( aConstVertex[1] >= -1.0f && aConstVertex[1] <= 1.0f && "Invalic cube y range\n" );
		assert( aConstVertex[2] >= -1.0f && aConstVertex[2] <= 1.0f && "Invalic cube z range\n" );

		_rVertex.m_oOnSphere.x = m_fScale * aConstVertex[0] * sqrtf( 1.0f - aConstVertex[1] * aConstVertex[1] * 0.5f - aConstVertex[2] * aConstVertex[2] * 0.5f + aConstVertex[1] * aConstVertex[1] * aConstVertex[2] * aConstVertex[2] / 3.0f );
		_rVertex.m_oOnSphere.y = m_fScale * aConstVertex[1] * sqrtf( 1.0f - aConstVertex[2] * aConstVertex[2] * 0.5f - aConstVertex[0] * aConstVertex[0] * 0.5f + aConstVertex[2] * aConstVertex[2] * aConstVertex[0] * aConstVertex[0] / 3.0f );
		_rVertex.m_oOnSphere.z = m_fScale * aConstVertex[2] * sqrtf( 1.0f - aConstVertex[0] * aConstVertex[0] * 0.5f - aConstVertex[1] * aConstVertex[1] * 0.5f + aConstVertex[0] * aConstVertex[0] * aConstVertex[1] * aConstVertex[1] / 3.0f );
	}

	void SphericubicMesh::CreateMeshFaces()
	{
		const unsigned uVerticesPerSide = m_uVerticesPerRow * m_uVerticesPerRow;
		const unsigned uFacesPerSide = m_uSubdivisions * m_uSubdivisions;
		m_uFacesCount = 6 * uFacesPerSide;
		m_uIndexesCount = 4 * m_uFacesCount;
		assert( 0xffff >= m_uIndexesCount );
		m_pIndexes = new unsigned short[m_uIndexesCount];
		m_pFaces = new Face[m_uFacesCount];
		m_vFaceToRenderList.reserve( m_uFacesCount );

		// assign local face indexes
		unsigned short* pIndexes = m_pIndexes;
		unsigned short uIndexOffset = 0;
		Face* pFace = m_pFaces;
		for ( unsigned int i = 0 ; m_uFacesCount > i ; ++i, ++pFace, pIndexes += 4, uIndexOffset += 4 )
		{
			pFace->m_pIndexes = pIndexes;
			pFace->m_uIndexOffset = uIndexOffset;
			pFace->m_pVertexes = m_pVertexes;
		}

		struct FaceDispatcher
		{
			unsigned int	m_uStartIndex;
			unsigned int	m_uVertexStartIndex;
			unsigned int	m_uVerticesPerRow;
			Vertex*			m_pVertexes;

			void Do(Face* _pFaces, const unsigned int& _uIndexOffset, const unsigned int& _uVertexIndexOffset)
			{
				Face& rFace = _pFaces[m_uStartIndex + _uIndexOffset];
				// vertex order for counter-clockwise triangle strip
				rFace.m_pIndexes[0] = m_uVertexStartIndex + _uVertexIndexOffset;
				rFace.m_pIndexes[1] = m_uVertexStartIndex + _uVertexIndexOffset + m_uVerticesPerRow;
				rFace.m_pIndexes[3] = m_uVertexStartIndex + _uVertexIndexOffset + m_uVerticesPerRow + 1;
				rFace.m_pIndexes[2] = m_uVertexStartIndex + _uVertexIndexOffset + 1;
				// compute vertexes and face normals.
				rFace.m_oFaceNormal.x = 0.0f;
				rFace.m_oFaceNormal.y = 0.0f;
				rFace.m_oFaceNormal.z = 0.0f;
				for ( unsigned int i = 0 ; 4 > i ; ++i )
				{
					rFace.m_oQuadNormals[i] = m_pVertexes[rFace.m_pIndexes[i]].m_oOnSphere;
					D3DXVec3Normalize( &rFace.m_oQuadNormals[i], &rFace.m_oQuadNormals[i] );
					rFace.m_oFaceNormal += rFace.m_oQuadNormals[i];
				}
				D3DXVec3Normalize( &rFace.m_oFaceNormal, &rFace.m_oFaceNormal );
				// compute bi normals
				Vector3 aVec[2];
				GetVector( rFace.m_pIndexes[0], rFace.m_pIndexes[2], aVec[0] );
				GetVector( rFace.m_pIndexes[0], rFace.m_pIndexes[1], aVec[1] );
				GetNormal( rFace.m_oBiNormals[0], aVec[0], aVec[1] );
				GetVector( rFace.m_pIndexes[1], rFace.m_pIndexes[2], aVec[0] );
				GetVector( rFace.m_pIndexes[1], rFace.m_pIndexes[3], aVec[1] );
				GetNormal( rFace.m_oBiNormals[1], aVec[0], aVec[1] );
			}

			void GetVector(const unsigned int& _uIndexA, const unsigned int& _uIndexB, Vector3& _rVec3)
			{
				_rVec3 = m_pVertexes[_uIndexB].m_oOnSphere - m_pVertexes[_uIndexA].m_oOnSphere;
			}

			void GetNormal(Vector3& _rfNormal, const Vector3& _rfU, const Vector3& _rfV)
			{
				_rfNormal.x = _rfU.y * _rfV.z - _rfU.z * _rfV.y;
				_rfNormal.y = _rfU.z * _rfV.x - _rfU.x * _rfV.z;
				_rfNormal.z = _rfU.x * _rfV.y - _rfU.y * _rfV.x;
				D3DXVec3Normalize( &_rfNormal, &_rfNormal );
			}
		};

		FaceDispatcher aDispatch[] =
		{
			// front
			{	0 * uFacesPerSide,	0 * uVerticesPerSide,	m_uVerticesPerRow, m_pVertexes },
			// back
			{	1 * uFacesPerSide,	1 * uVerticesPerSide,	m_uVerticesPerRow, m_pVertexes },
			// top
			{	2 * uFacesPerSide,	2 * uVerticesPerSide,	m_uVerticesPerRow, m_pVertexes },
			// bottom
			{	3 * uFacesPerSide,	3 * uVerticesPerSide,	m_uVerticesPerRow, m_pVertexes },
			// left
			{	4 * uFacesPerSide,	4 * uVerticesPerSide,	m_uVerticesPerRow, m_pVertexes },
			// right
			{	5 * uFacesPerSide,	5 * uVerticesPerSide,	m_uVerticesPerRow, m_pVertexes },
		};

		for ( unsigned int i = 0 ; uFacesPerSide > i ; ++i )
		{
			const unsigned int uVertexIndex = ( i % m_uSubdivisions ) + ( i / m_uSubdivisions ) * m_uVerticesPerRow;
			aDispatch[0].Do( m_pFaces, i, uVertexIndex );
			aDispatch[1].Do( m_pFaces, i, uVertexIndex );
			aDispatch[2].Do( m_pFaces, i, uVertexIndex );
			aDispatch[3].Do( m_pFaces, i, uVertexIndex );
			aDispatch[4].Do( m_pFaces, i, uVertexIndex );
			aDispatch[5].Do( m_pFaces, i, uVertexIndex );
		}
	}

	bool SphericubicMesh::CreateVertexBuffer()
	{
		HRESULT hResult = m_rRenderer.GetDevice()->CreateVertexBuffer( m_uVerticesCount * sizeof( Vertex ),
			D3DUSAGE_WRITEONLY,
			0,
			D3DPOOL_MANAGED,
			&m_pVertexBuffer,
			NULL);
		if ( FAILED( hResult) )
		{
			return false;
		}

		void* pLockedBuffer = NULL;
		hResult = m_pVertexBuffer->Lock( 0, 0, &pLockedBuffer, D3DLOCK_DISCARD );
		if ( FAILED( hResult) )
		{
			return false;
		}
		memcpy( pLockedBuffer, m_pVertexes, m_uVerticesCount * sizeof( Vertex ) );
		m_pVertexBuffer->Unlock();

		hResult = m_rRenderer.GetDevice()->CreateIndexBuffer( m_uIndexesCount * sizeof( unsigned short ),
			D3DUSAGE_WRITEONLY,
			D3DFMT_INDEX16,
			D3DPOOL_MANAGED,
			&m_pIndexBuffer,
			NULL );
		if ( FAILED( hResult) )
		{
			return false;
		}
		hResult = m_pIndexBuffer->Lock( 0, 0, &pLockedBuffer, D3DLOCK_DISCARD );
		if ( FAILED( hResult) )
		{
			return false;
		}
		memcpy( pLockedBuffer, m_pIndexes, m_uIndexesCount * sizeof( unsigned short ) );
		m_pIndexBuffer->Unlock();

		D3DVERTEXELEMENT9 aVertexElement[] =
		{
			{	0, 3 * sizeof( float ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
			D3DDECL_END()
		};
		hResult = m_rRenderer.GetDevice()->CreateVertexDeclaration( aVertexElement, &m_pVertexDecl );
		if ( FAILED( hResult) )
		{
			return false;
		}

		return true;
	}

	//--------------------------------------------------------------------
	//--------------------------------------------------------------------
	//--------------------------------------------------------------------

	bool SphericubicMesh::Face::IsFacingDir(const Vector3 _rCamPos, const Matrix& _rTransf)
	{
		// implementation based on http://jeux.developpez.com/faq/3d/?page=culling#TECHNIQUES_CULLING_backface
		Matrix oTransfNoTrans = _rTransf;
		Vector3 oDir;
		Vector3 oVertex;
		Vector4 oTransfVertex4;
		Vector3 oTransfNormal;

		// don't apply translation for normal transformation.
		oTransfNoTrans._41 = oTransfNoTrans._42 = oTransfNoTrans._43 = 0.0f;

		for ( unsigned int i = 0 ; 2 > i ; ++i )
		{
			const unsigned int uIndex = i * 3;
			Vertex& rVertex = m_pVertexes[m_pIndexes[uIndex]];

			if ( FLT_MAX == rVertex.m_oFromCamDir.x )
			{
				D3DXVec3Transform( &oTransfVertex4, &rVertex.m_oOnSphere, &_rTransf );
				rVertex.m_oFromCamDir = Vector3( oTransfVertex4 ) - _rCamPos;
				D3DXVec3Normalize( &rVertex.m_oFromCamDir, &rVertex.m_oFromCamDir );
			}

			m_fBiCosines[i] = FLT_MAX;
			D3DXVec3TransformNormal( &oTransfNormal, &m_oBiNormals[i], &oTransfNoTrans );
			m_fBiCosines[i] = D3DXVec3Dot( &rVertex.m_oFromCamDir, &oTransfNormal );

			if ( 0.0f >= m_fBiCosines[i] )
			{
				return true;
			}
		}

		return false;
	}
}
